﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using UnityEngine;

namespace UnityHotFix
{
    /// <summary>
    /// 资源管理系统
    /// </summary>
    public static class Resource
    {
        public const string LocalRootFolderNmae = "Data";

        public static string LocalDirectory { get; set; }
        public static string NetworkAddress { get; set; }
        public static string NetworkDirectory { get; set; }

        private static Dictionary<string, ResourceMetadata> allMetadata = new Dictionary<string, ResourceMetadata>();
        private static Dictionary<string, ResourceObject> resObjects = new Dictionary<string, ResourceObject>();
        private static Thread loadThread = null;
        private static Thread downloadThread = null;
        private static List<ResourceObject> loadList = new List<ResourceObject>();
        private static List<ResourceObject> saveList = new List<ResourceObject>();
        private static List<ResourceObject> downloadList = new List<ResourceObject>();
        private static List<ResourceObject> completeList = new List<ResourceObject>();

        private static int completeLock = 0;

        private static UnityLoop unityLoop = null;
        static Resource()
        {
            PathInit();

            loadThread = new Thread(LoadThread);
            loadThread.Start();
            downloadThread = new Thread(DownloadThread);
            downloadThread.Start();

            GameObject gameObject = new GameObject("_UnityHotFix_");
            unityLoop = gameObject.AddComponent<UnityLoop>();
            gameObject.hideFlags = HideFlags.HideInHierarchy;
            UnityEngine.Object.DontDestroyOnLoad(gameObject);
        }
        private static void PathInit()
        {
            switch (Application.platform)
            {
                case RuntimePlatform.WindowsEditor:
                    LocalDirectory = Directory.GetCurrentDirectory() + @"/" + LocalRootFolderNmae + "/Windows/";
                    NetworkAddress = @"http://htdw.oss-cn-shanghai.aliyuncs.com/CQ/Windows/";
                    NetworkDirectory = "CQ/Windows/";
                    break;
                case RuntimePlatform.OSXEditor:
                    LocalDirectory = Directory.GetCurrentDirectory() + @"/" + LocalRootFolderNmae + "/macOS/";
                    NetworkAddress = @"http://htdw.oss-cn-shanghai.aliyuncs.com/CQ/macOS/";
                    NetworkDirectory = "CQ/macOS/";
                    break;
                case RuntimePlatform.WindowsPlayer:
                    LocalDirectory = Application.dataPath + @"/" + LocalRootFolderNmae + "/";
                    NetworkAddress = @"http://htdw.oss-cn-shanghai.aliyuncs.com/CQ/Windows/";
                    NetworkDirectory = "CQ/Windows/";
                    break;
                case RuntimePlatform.Android:
                    LocalDirectory = Application.persistentDataPath + @"/" + LocalRootFolderNmae + "/";
                    NetworkAddress = @"http://htdw.oss-cn-shanghai.aliyuncs.com/CQ/Android/";
                    NetworkDirectory = "CQ/Android/";
                    break;
                case RuntimePlatform.IPhonePlayer:
                    LocalDirectory = Application.persistentDataPath + @"/" + LocalRootFolderNmae + "/";
                    NetworkAddress = @"http://htdw.oss-cn-shanghai.aliyuncs.com/CQ/iOS/";
                    NetworkDirectory = "CQ/iOS/";
                    break;
                default:
                    Debug.LogError("资源管理系统未设置该平台默认目录和地址：" + Application.platform);
                    break;
            }
        }
        // 负责读取磁盘文件、写入磁盘文件、解密、解码、Unity类型之外的类型对象生成
        private static void LoadThread()
        {
            ResourceObject resObject;
            while (true)
            {
                resObject = null;
                lock (loadList)
                {
                    if (loadList.Count > 1)
                    {
                        resObject = loadList[0];
                        foreach (var item in loadList)
                        {
                            if (item.priority < resObject.priority)
                            {
                                resObject = item;
                            }
                        }
                        loadList.Remove(resObject);
                    }
                    else if (loadList.Count > 0)
                    {
                        resObject = loadList[0];
                        loadList.RemoveAt(0);
                    }
                }
                if (resObject != null)
                {
                    try
                    {
                        ResourceMetadata objectMetadata;
                        if (allMetadata.TryGetValue(resObject.name, out objectMetadata))
                        {
                            DateTime dateTime = File.GetLastWriteTimeUtc(LocalDirectory + resObject.name);
                            if (dateTime < objectMetadata.LastModified)
                            {
                                Debug.Log("下载文件(" + resObject.name + ") 本地(" + dateTime + ") 网络(" + objectMetadata.LastModified + ")");
                                lock (downloadList)
                                {
                                    resObject.state = ResourceState.Downloading;
                                    downloadList.Add(resObject);
                                }
                            }
                            else
                            {
                                Debug.Log("读取文件(" + resObject.name + ") 本地(" + dateTime + ") 网络(" + objectMetadata.LastModified + ")");
                                resObject.state = ResourceState.Reading;
                                resObject.bytes = File.ReadAllBytes(LocalDirectory + resObject.name);
                                resObject.state = ResourceState.Generateing;
                                while (0 != Interlocked.Exchange(ref completeLock, 1)) { Thread.Sleep(10); }
                                completeList.Add(resObject);
                                Interlocked.Exchange(ref completeLock, 0);
                            }
                        }
                        else
                        {
                            resObject.state = ResourceState.Error;
                            Debug.LogError("资源文件不存在：" + NetworkAddress + resObject.name);
                            while (0 != Interlocked.Exchange(ref completeLock, 1)) { Thread.Sleep(10); }
                            completeList.Add(resObject);
                            Interlocked.Exchange(ref completeLock, 0);
                        }
                    }
                    catch (Exception e)
                    {
                        resObject.state = ResourceState.Error;
                        Debug.LogError("加载文件出错：" + NetworkAddress + resObject.name + " Exception=" + e);
                        while (0 != Interlocked.Exchange(ref completeLock, 1)) { Thread.Sleep(10); }
                        completeList.Add(resObject);
                        Interlocked.Exchange(ref completeLock, 0);
                    }
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
        }
        // 负责下载远程文件
        private static void DownloadThread()
        {
            ResourceObject resObject;
            while (true)
            {
                resObject = null;
                lock (downloadList)
                {
                    if (downloadList.Count > 1)
                    {
                        resObject = downloadList[0];
                        foreach (var item in downloadList)
                        {
                            if (item.priority < resObject.priority)
                            {
                                resObject = item;
                            }
                        }
                        downloadList.Remove(resObject);
                    }
                    else if (downloadList.Count > 0)
                    {
                        resObject = downloadList[0];
                        downloadList.RemoveAt(0);
                    }
                }
                if (resObject != null)
                {
                    try
                    {
                        HttpWebRequest request = WebRequest.Create(NetworkAddress + NetworkDirectory + resObject.name) as HttpWebRequest;
                        HttpWebResponse response = request.GetResponse() as HttpWebResponse;
                        int size = (int)response.ContentLength;
                        Stream responseStream = response.GetResponseStream();
                        resObject.bytes = new byte[size];
                        int recvCount = 0;
                        while (recvCount < size)
                        {
                            recvCount += responseStream.Read(resObject.bytes, recvCount, size - recvCount);
                            //Debug.Log("下载进度：" + recvCount + "/" + size);
                        }
                        responseStream.Close();

                        if (recvCount == size)
                        {
                            string path = LocalDirectory + resObject.name;
                            Directory.CreateDirectory(Path.GetDirectoryName(path));
                            File.WriteAllBytes(path, resObject.bytes);
                            //File.SetLastWriteTimeUtc(path, ossObject.Metadata.LastModified);
                            resObject.state = ResourceState.Generateing;
                        }
                        else
                        {
                            resObject.state = ResourceState.Error;
                            Debug.LogError("下载远程文件出错：ContentLength=" + size + "recvCount=" + recvCount);
                        }
                    }
                    catch (Exception e)
                    {
                        resObject.state = ResourceState.Error;
                        Debug.LogError("下载远程文件出错：" + NetworkAddress + resObject.name + " Exception=" + e);
                    }

                    while (0 != Interlocked.Exchange(ref completeLock, 1)) { Thread.Sleep(10); }
                    completeList.Add(resObject);
                    Interlocked.Exchange(ref completeLock, 0);
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
        }
        // 负责Unity类型的对象生成、完成通知调用
        internal static void UnityUpdate()
        {
            if (0 == Interlocked.Exchange(ref completeLock, 1))
            {
                foreach (ResourceObject resObject in completeList)
                {
                    if (resObject.state == ResourceState.Error)
                    {
                        resObject.completeCallback?.Invoke(resObject);
                        continue;
                    }
                    //Debug.Log("解析资源 path=" + resObject.path);
                    switch (resObject.type)
                    {
                        case ResourceType.Binary:
                            {
                                resObject.state = ResourceState.Success;
                                resObject.completeCallback?.Invoke(resObject);
                            }
                            break;
                        case ResourceType.Text:
                            {
                                resObject.completeObject = Encoding.UTF8.GetString(resObject.bytes, 0, resObject.bytes.Length);
                                resObject.state = ResourceState.Success;
                                //Debug.Log(Time.frameCount + "  解析资源完成 path=" + resObject.path);
                                resObject.completeCallback?.Invoke(resObject);
                            }
                            break;
                        case ResourceType.Audio:
                            break;
                        case ResourceType.Video:
                            break;
                        case ResourceType.Image:
                            {
                                Texture2D texture2D = new Texture2D(8, 8);
                                texture2D.LoadImage(resObject.bytes);
                                resObject.completeObject = texture2D;
                                resObject.state = ResourceState.Success;
                                //Debug.Log(Time.frameCount + "  解析资源完成 path=" + resObject.path);
                                resObject.completeCallback?.Invoke(resObject);
                            }
                            break;
                        case ResourceType.AssetBundle:
                            {
                                unityLoop.StartCoroutine(AsyncGenerate(resObject));
                            }
                            break;
                    }
                }
                completeList.Clear();
                Interlocked.Exchange(ref completeLock, 0);
            }
        }
        private static IEnumerator AsyncGenerate(ResourceObject resObject)
        {
            AssetBundleCreateRequest assetBundleCreateRequest = AssetBundle.LoadFromMemoryAsync(resObject.bytes);
            yield return assetBundleCreateRequest;
            resObject.completeObject = assetBundleCreateRequest.assetBundle;
            resObject.state = ResourceState.Success;
            //Debug.Log(Time.frameCount + "  解析资源完成 path=" + resObject.path);
            resObject.completeCallback?.Invoke(resObject);
        }
        private static ResourceObject Load(string path, Type type, int priority, Action<ResourceObject> completeCallback)
        {
            ResourceObject resObject;
            if (resObjects.ContainsKey(path))
            {
                resObject = resObjects[path];
                if (priority < resObject.priority)
                {
                    resObject.priority = priority;
                }
            }
            else
            {
                ResourceType rtype = ResourceType.Binary;
                if (type == typeof(string))
                {
                    rtype = ResourceType.Text;
                }
                else if (type == typeof(Texture2D))
                {
                    rtype = ResourceType.Image;
                }
                else if (type == typeof(AssetBundle))
                {
                    rtype = ResourceType.AssetBundle;
                }
                resObject = new ResourceObject { name = path, type = rtype, priority = priority, state = ResourceState.Waiting };
                resObjects.Add(path, resObject);
                lock (loadList)
                {
                    loadList.Add(resObject);
                }
            }
            if (completeCallback != null)
            {
                if (resObject.state == ResourceState.Success || resObject.state == ResourceState.Error)
                {
                    completeCallback(resObject);
                }
                else
                {
                    resObject.completeCallback += completeCallback;
                }
            }
            return resObject;
        }
        public static ResourceObject Load<T>(string path)
        {
            return Load(path, typeof(T), 1, null);
        }
        public static ResourceObject Load<T>(string path, int priority)
        {
            return Load(path, typeof(T), priority, null);
        }
        public static ResourceObject Load<T>(string path, Action<ResourceObject> completeCallback)
        {
            return Load(path, typeof(T), 1, completeCallback);
        }
        public static ResourceObject Load<T>(string path, int priority, Action<ResourceObject> completeCallback)
        {
            return Load(path, typeof(T), priority, completeCallback);
        }
        public static void Unload(string path)
        {
        }
        public static void UnloadAll()
        {
            lock (loadList)
            {
                loadList.Clear();
            }
            lock (downloadList)
            {
                downloadList.Clear();
            }
            lock (resObjects)
            {
                foreach (var item in resObjects)
                {
                    item.Value.bytes = null;
                    item.Value.completeObject = null;
                    item.Value.completeCallback = null;
                    item.Value.state = ResourceState.Error;
                }
                resObjects.Clear();
            }
        }
    }
}